/*
 * Copyright 2000 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <httpd.h>
#include <util_md5.h>
#include "WtSession.h"
#include "WtUtil.h"
#include "WtDbSession.h"
#include "WtInProcSession.h"
#include "WtContext.h"
#include "WtTable.h"
#include "WtTableUtil.h"
#include "WtCookie.h"
#include "WtClientRequest.h"

/* Initialize the session */

void WtInitSession(WtSession *s)
{
  s->clientId = NULL;
  s->clientValue = WtNewString(NULL);
  s->clientTimeStamp = -1;
  s->cookieName = WtNewString("WtSessionId");
  Tcl_IncrRefCount(s->cookieName);
  s->cookie = NULL;
  s->cookieSendMode = WtNewString("default");
  Tcl_IncrRefCount(s->cookieSendMode);
  s->maxIdleTime = 86400;
  s->sweepFrequency = 0.001;
  s->handler = WtNewString("::wt::session::dbSession::dbSessionHandler");
  Tcl_IncrRefCount(s->handler);
  s->handlerType = WtNewString("proc");
  Tcl_IncrRefCount(s->handlerType);
  s->handlerLoaded = 0;
  s->dbSession = WtNewDbSessionObj();
  /* s->inProcSession = WtNewProcSession(); */
  s->inProcSession = NULL;
  s->isInitialized = 1;
}

/* Delete the session */

void WtDeleteSession(WtSession *s)
{
  s->isInitialized = 0;

  WtDecr(s->clientId);
  s->clientId = NULL;

  WtDecr(s->clientValue);
  s->clientValue = NULL;

  WtDecr(s->cookieName);
  s->cookieName = NULL;

  WtDecr(s->cookie);
  s->cookie = NULL;

  WtDecr(s->cookieSendMode);
  s->cookieSendMode = NULL;

  WtDecr(s->handler);
  s->handler = NULL;

  WtDecr(s->handlerType);
  s->handlerType = NULL;

  WtDecr(s->dbSession);
  s->dbSession = NULL;

  WtDecr(s->inProcSession);
  s->inProcSession = NULL;
}

/* Get the initial session ID */

Tcl_Obj *WtGetInitialSessionId(WtSession *s, int *isAutoIdPtr, Tcl_Interp *interp)
{
  Tcl_Obj *id = NULL;
  int isAutoId = 0;

  if (WtGetClientSessionId(s, interp)) {
    if (Tcl_GetCharLength(s->clientId)) {
      id = s->clientId;
      isAutoId = 0;
    } else if (id = WtGenerateSessionId(s, interp)) {
      isAutoId = 1;
    }
  }

  if (id && isAutoIdPtr) {
    *isAutoIdPtr = isAutoId;
  }

  return id;
}

/* Get the client-supplied session ID */

Tcl_Obj *WtGetClientSessionId(WtSession *s, Tcl_Interp *interp)
{
  int ok = 1;
  long tsVal;
  WtContext *w = WtGetAssocContext(interp);
  Tcl_Obj *obj = NULL, *ts;
  char *val, *p;

  if (!s->clientId) {
    if (Tcl_GetCharLength(s->cookieName)) {
      if (!WtReadRequestCookies(w, interp)) {
        ok = 0;
      } else if (!WtConvertToTable(w->web->clientRequest.cookies,
          interp)) {
        ok = 0;
      } else if (obj = WtTableGet(w->web->clientRequest.cookies,
          s->cookieName)) {
        s->clientValue = obj;
        Tcl_IncrRefCount(s->clientValue);

        val = WtToString(obj);
        if (p = strchr(val, '.')) {
          s->clientId = Tcl_NewStringObj(val, p - val);
          Tcl_IncrRefCount(s->clientId);

          ts = Tcl_NewStringObj(p + 1, -1);
          Tcl_IncrRefCount(ts);

          if (Tcl_GetLongFromObj(interp, ts, &tsVal) == TCL_OK) {
            if (tsVal > 0) {
              s->clientTimeStamp = (time_t)tsVal;
            }
          }

          Tcl_DecrRefCount(ts);
        } else {
          s->clientId = obj;
          Tcl_IncrRefCount(s->clientId);
        }
      }
    }

    if (ok && !s->clientId) {
      s->clientValue = WtNewString(NULL);
      Tcl_IncrRefCount(s->clientValue);
      s->clientId = WtNewString(NULL);
      Tcl_IncrRefCount(s->clientId);
    }
  }

  return ok ? s->clientId : NULL;
}

/* Generate a new session ID */

Tcl_Obj *WtGenerateSessionId(WtSession *s, Tcl_Interp *interp)
{
  Tcl_Obj *ret = NULL, *src, *obj;
  char *bytes;
  int len;
  WtContext *w = WtGetAssocContext(interp);

  if (WtConvertToTable(w->web->serverVars, interp)) {
    src = Tcl_NewStringObj(NULL, 0);

    /* Use mod_unique_id */

    if ((obj = WtTableGetObjFromStr(w->web->serverVars,
        "UNIQUE_ID", NULL)) && Tcl_GetCharLength(obj)) {
      Tcl_AppendObjToObj(src, obj);
    }

    /* Use environment (weak) */

    WtSessionAddEnvironment(src);
    if ((obj = WtTableGetObjFromStr(w->web->serverVars,
        "REMOTE_ADDR", NULL)) && Tcl_GetCharLength(obj)) {
      Tcl_AppendObjToObj(src, obj);
    }
    if (w->apServer->server_hostname) {
      Tcl_AppendToObj(src, w->apServer->server_hostname, -1);
    }
    Tcl_AppendStringsToObj(src, __FILE__, __TIME__, "baboo", NULL);

    bytes = Tcl_GetStringFromObj(src, &len);
    ret = Tcl_NewStringObj(ap_md5_binary(w->apReq->pool, bytes, len), -1);

    Tcl_DecrRefCount(src);
  }

  return ret;
}

void WtSessionAddEnvironment(Tcl_Obj *str)
{
  char buf[512];

#ifdef WIN32

  FILETIME st, ct, et, kt, ut;
  HANDLE process;

  GetSystemTimeAsFileTime(&st);

  if (process = GetCurrentProcess()) {
    GetProcessTimes(process, &ct, &et, &kt, &ut);
  }

  ap_snprintf(buf, sizeof(buf), "%d%d%d%d%d%d%d%d%d%d%d%d%c",
    GetCurrentProcessId(), GetCurrentThreadId(),
    st.dwLowDateTime, st.dwHighDateTime,
    ct.dwLowDateTime, ct.dwHighDateTime,
    et.dwLowDateTime, et.dwHighDateTime,
    kt.dwLowDateTime, kt.dwHighDateTime,
    ut.dwLowDateTime, ut.dwHighDateTime,
    buf[ut.dwLowDateTime % sizeof(buf)]);

#else

  struct timeval tv;

  gettimeofday(&tv, NULL);

  ap_snprintf(buf, sizeof(buf), "%ld%ld%ld%ld%c", src, buf,
    getpid(), gettid(), tv.tv_sec, tv.tv_usec,
    buf[tv.tv_usec % sizeof(buf)]);

#endif /* WIN32 */

  Tcl_AppendToObj(str, buf, -1);
}

/* Get the output cookie */

Tcl_Obj *WtGetSessionCookie(WtSession *s, Tcl_Interp *interp)
{
  if (!s->cookie) {
    s->cookie = WtNewCookieObj();
    Tcl_IncrRefCount(s->cookie);
    if (Tcl_GetCharLength(s->cookieName)) {
      if (!WtCookieSetName(s->cookie, s->cookieName, interp)) {
        Tcl_DecrRefCount(s->cookie);
        s->cookie = NULL;
      }
    }
  }

  return s->cookie;
}

/* Set the cookie name */

int WtSetSessionCookieName(WtSession *s, Tcl_Obj *name, Tcl_Interp *interp)
{
  /* Client cookie name */

  Tcl_DecrRefCount(s->cookieName);
  s->cookieName = name;
  Tcl_IncrRefCount(s->cookieName);

  /* Output cookie */

  if (WtGetSessionCookie(s, interp)) {
    WtCopyOnWrite(&s->cookie);
    return WtCookieSetName(s->cookie, name, interp);
  }

  return 0;
}

/* Send the cookie */

int WtSetSessionCookie(WtSession *s, Tcl_Obj *id, Tcl_Interp *interp,
    time_t timeStamp)
{
  int ok = 1, doSend = 0;
  WtContext *w;
  Tcl_Obj *val, *atimeObj;

  if (id && Tcl_GetCharLength(id)) {
    if (!strcmp(WtToString(s->cookieSendMode), "default")) {
      if (!s->clientId || !Tcl_GetCharLength(s->clientId) ||
          strcmp(WtToString(s->clientId), WtToString(id)) ||
          timeStamp != s->clientTimeStamp) {
        doSend = 1;
      }
    } else if (Tcl_GetBooleanFromObj(interp, s->cookieSendMode,
        &doSend) != TCL_OK) {
      ok = 0;
    }

    if (ok && doSend) {
      if (!WtGetSessionCookie(s, interp)) {
        ok = 0;
      } else {
        WtCopyOnWrite(&s->cookie);

        if (timeStamp == -1) {
          WtCookieSetValue(s->cookie, id, interp);
        } else {
          val = Tcl_DuplicateObj(id);
          Tcl_IncrRefCount(val);
          Tcl_AppendToObj(val, ".", 1);

          atimeObj = Tcl_NewLongObj((long)timeStamp);
          Tcl_IncrRefCount(atimeObj);
          Tcl_AppendObjToObj(val, atimeObj);
          Tcl_DecrRefCount(atimeObj);

          WtCookieSetValue(s->cookie, val, interp);

          Tcl_DecrRefCount(val);
        }

        /* Store in the cookie jar */

        w = WtGetAssocContext(interp);

        if (!WtRemoveFromCln(w->web->clientResponse.cookies,
              s->cookieName, -1, interp) ||
            !WtAppendClnItem(w->web->clientResponse.cookies,
              s->cookie, interp)) {
          ok = 0;
        }
      }
    }
  }

  return ok;
}

/* Set the handler */

int WtSetSessionHandler(WtSession *s, Tcl_Obj *handler, Tcl_Interp *interp)
{
  int ok = 0;
  if (!Tcl_GetCharLength(handler)) {
    Tcl_AppendResult(interp, "Session handler string is invalid.");
  } else if (!strcmp(WtToString(s->handler), WtToString(handler))) {
    ok = 1;
  } else if (!s->handlerLoaded || WtUnloadSessionHandler(s, interp)) {
    Tcl_DecrRefCount(s->handler);
    s->handler = handler;
    Tcl_IncrRefCount(s->handler);
    ok = 1;
  }

  return ok;
}

/* Load the handler */

int WtLoadSessionHandler(WtSession *s, Tcl_Interp *interp)
{
  int ok = 0;

  if (s->handlerLoaded) {
    ok = 1;
  } else if (WtSessionHandlerStr(s, s->handler, "loadModule",
      interp, 0, NULL) == TCL_OK) {
    s->handlerLoaded = 1;
    ok = 1;
  }

  return ok;
}

/* Unload the handler */

int WtUnloadSessionHandler(WtSession *s, Tcl_Interp *interp)
{
  int ok = 0;

  if (!s->handlerLoaded) {
    ok = 1;
  } else if (WtSessionHandlerStr(s, s->handler,
      "unloadModule", interp, 0, NULL) == TCL_OK) {
    s->handlerLoaded = 0;
    ok = 1;
  }

  return ok;
}

/* Check if we need to sweep the sessions */

int WtShouldSweepSessions(WtSession *s, Tcl_Interp *interp)
{
  if (s->sweepFrequency > 0 &&
      ((rand() * 1.0) / (RAND_MAX * 1.0)) <= s->sweepFrequency) {
    return 1;
  }
  return 0;
}

/* Finalize the request */

int WtSessionEndRequest(WtSession *s, Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_Obj *err, *msg;

  msg = WtNewString(NULL);
  Tcl_IncrRefCount(msg);

  if (s->handlerLoaded) {
    if (WtSessionHandlerStr(s, s->handler, "finalizeRequest",
        interp, 0, NULL) != TCL_OK) {
      err = Tcl_GetVar2Ex(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY);
      Tcl_AppendObjToObj(msg, err);
      ok = 0;
    }
  }

  if (!WtUnloadSession(s, interp)) {
    err = Tcl_GetVar2Ex(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY);
    if (Tcl_GetCharLength(msg)) {
      Tcl_AppendToObj(msg, "\n    and\n", -1);
    }
    Tcl_AppendObjToObj(msg, err);
    ok = 0;
  }

  if (!ok) {
    Tcl_ResetResult(interp);
    Tcl_AddObjErrorInfo(interp, WtToString(msg), -1);
  }

  Tcl_DecrRefCount(msg);

  return ok;
}

/* Unload the session module */

int WtUnloadSession(WtSession *s, Tcl_Interp *interp)
{
  int ok = 1;

  if (s->handlerLoaded && !WtUnloadSessionHandler(s, interp)) {
    ok = 0;
  }

  return ok;
}

/* session command */

int WtSessionCmd(ClientData clientData, Tcl_Interp *interp,
                 int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  char *subCmd;
  WtSession *s = WtGetAssocSession(interp);

  if (objc == 1) {

    WtSessionCmdUsage(interp, objv[0]);

  } else if (objc >= 2) {

    subCmd = Tcl_GetString(objv[1]);

    if (!strcmp(subCmd, "clientId")) {
      ret = WtSessionClientIdCmd(clientData, interp, s, objc, objv);
    } else if (!strcmp(subCmd, "newId")) {
      ret = WtSessionNewIdCmd(clientData, interp, s, objc, objv);
    } else if (!strcmp(subCmd, "cookie")) {
      ret = WtSessionCookieCmd(clientData, interp, s, objc, objv);
    } else if (!strcmp(subCmd, "cookieName")) {
      ret = WtSessionCookieNameCmd(clientData, interp, s, objc, objv);
    } else if (!strcmp(subCmd, "cookieSendMode")) {
      ret = WtSessionCookieSendModeCmd(clientData, interp, s, objc, objv);
    } else if (!strcmp(subCmd, "maxIdleTime")) {
      ret = WtSessionMaxIdleTimeCmd(clientData, interp, s, objc, objv);
    } else if (!strcmp(subCmd, "sweepFrequency")) {
      ret = WtSessionSweepFrequencyCmd(clientData, interp, s, objc, objv);
    } else if (!strcmp(subCmd, "handler")) {
      ret = WtSessionHandlerCmd(clientData, interp, s, objc, objv);
    } else if (!strcmp(subCmd, "handlerType")) {
      ret = WtSessionHandlerTypeCmd(clientData, interp, s, objc, objv);
    } else {
      ret = WtSessionHandler(s, s->handler, objv[1], interp,
        objc - 2, objv + 2);
    }
  }

  return ret;
}

/* clientId comand */

int WtSessionClientIdCmd(ClientData clientData, Tcl_Interp *interp,
    WtSession *s, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *id;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]));
  } else if (id = WtGetClientSessionId(s, interp)) {
    Tcl_SetObjResult(interp, id);
    ret = TCL_OK;
  }

  return ret;
}

/* newId comand */

int WtSessionNewIdCmd(ClientData clientData, Tcl_Interp *interp,
    WtSession *s, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *id;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]));
  } else if (id = WtGenerateSessionId(s, interp)) {
    Tcl_SetObjResult(interp, id);
    ret = TCL_OK;
  }

  return ret;
}

/* cookieName command */

int WtSessionCookieNameCmd(ClientData clientData, Tcl_Interp *interp,
    WtSession *s, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?cookieName?", NULL);
  } else if (objc == 2) {
    Tcl_SetObjResult(interp, s->cookieName);
    ret = TCL_OK;
  } else if (objc == 3) {
    if (WtSetSessionCookieName(s, objv[2], interp)) {
      Tcl_SetObjResult(interp, s->cookieName);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* cookie command */

int WtSessionCookieCmd(ClientData clientData, Tcl_Interp *interp,
    WtSession *s, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *cookie;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?cookie?", NULL);
  } else {
    if (objc == 2) {
      if (cookie = WtGetSessionCookie(s, interp)) {
        Tcl_SetObjResult(interp, cookie);
        ret = TCL_OK;
      }
    } else if (objc == 3) {
      if (s->cookie != objv[2]) {
        if (s->cookie) {
          Tcl_DecrRefCount(s->cookie);
        }
        s->cookie = objv[2];
        Tcl_IncrRefCount(s->cookie);
      }
      Tcl_SetObjResult(interp, s->cookie);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* cookieSendMode command */

int WtSessionCookieSendModeCmd(ClientData clientData, Tcl_Interp *interp,
    WtSession *s, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, intVal;
  const char *str;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?cookieSendMode?", NULL);
  } else if (objc == 2) {
    Tcl_SetObjResult(interp, s->cookieSendMode);
    ret = TCL_OK;
  } else if (objc == 3) {
    str = WtToString(objv[2]);
    if (!strcmp(str, "default") ||
        Tcl_GetBooleanFromObj(interp, objv[2], &intVal) == TCL_OK) {
      Tcl_DecrRefCount(s->cookieSendMode);
      s->cookieSendMode = objv[2];
      Tcl_IncrRefCount(s->cookieSendMode);
      Tcl_SetObjResult(interp, s->cookieSendMode);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* maxIdleTime comand */

int WtSessionMaxIdleTimeCmd(ClientData clientData, Tcl_Interp *interp,
    WtSession *s, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, intVal;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?maxIdleTime?", NULL);
  } else if (objc == 2) {
    Tcl_SetObjResult(interp, Tcl_NewIntObj(s->maxIdleTime));
    ret = TCL_OK;
  } else if (objc == 3) {
    if (Tcl_GetIntFromObj(interp, objv[2], &intVal) == TCL_OK) {
      s->maxIdleTime = intVal;
      ret = TCL_OK;
    }
  }

  return ret;
}

/* sweepFrequency comand */

int WtSessionSweepFrequencyCmd(ClientData clientData, Tcl_Interp *interp,
    WtSession *s, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  double dblVal;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?sweepFrequency", NULL);
  } else if (objc == 2) {
    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(s->sweepFrequency));
    ret = TCL_OK;
  } else if (objc == 3) {
    if (Tcl_GetDoubleFromObj(interp, objv[2], &dblVal) == TCL_OK) {
      s->sweepFrequency = dblVal;
      ret = TCL_OK;
    }
  }

  return ret;
}

/* handler command */

int WtSessionHandlerCmd(ClientData clientData, Tcl_Interp *interp,
    WtSession *s, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
    WtToString(objv[0]), " ", WtToString(objv[1]), " ?handler?");
  } else if (objc == 2) {
    Tcl_SetObjResult(interp, s->handler);
    ret = TCL_OK;
  } else if (WtSetSessionHandler(s, objv[2], interp)) {
    Tcl_SetObjResult(interp, s->handler);
    ret = TCL_OK;
  }

  return ret;
}

/* handlerType command */

int WtSessionHandlerTypeCmd(ClientData clientData, Tcl_Interp *interp,
    WtSession *s, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  const char *typeStr;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
    WtToString(objv[0]), " ", WtToString(objv[1]), " ?type?");
  } else if (objc == 2) {
    Tcl_SetObjResult(interp, s->handlerType);
    ret = TCL_OK;
  } else {
    typeStr = WtToString(objv[2]);
    if (strcmp(typeStr, "proc") && strcmp(typeStr, "namespace") &&
        strcmp(typeStr, "")) {
      Tcl_AppendResult(interp, "Invalid session handler type: \"",
        typeStr, "\".");
    } else {
      Tcl_DecrRefCount(s->handlerType);
      s->handlerType = objv[2];
      Tcl_IncrRefCount(s->handlerType);
      Tcl_SetObjResult(interp, s->handlerType);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* Get the session associated with an interp */

WtSession *WtGetAssocSession(Tcl_Interp *interp)
{
  WtContext *w = WtGetAssocContext(interp);
  return w && w->web ? &w->web->session : NULL;
}

/* Create the interp commands */

void WtInitSessionCommands(Tcl_Interp *interp)
{
  WtContext *w = Tcl_GetAssocData(interp, "wt::context", NULL);

  WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, w,
    "Wtcl: WtInitSessionCommands.");

  Tcl_CreateObjCommand(interp, "::wt::session::session",
                       WtSessionCmd, NULL, NULL);
}

/* Invoke handler */

int WtSessionHandler(WtSession *s, Tcl_Obj *handler,
    Tcl_Obj *method, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  return WtSessionHandlerInternal(s, handler, method,
    interp, objc, objv);
}

/* Using a C string */

int WtSessionHandlerStr(WtSession *s, Tcl_Obj *handler,
    const char *methodStr, Tcl_Interp *interp, int objc, 
    Tcl_Obj *const objv[])
{
  int ret;
  Tcl_Obj *method = WtNewString(methodStr);
  Tcl_IncrRefCount(method);
  ret = WtSessionHandlerInternal(s, handler, method,
    interp, objc, objv);
  Tcl_DecrRefCount(method);
  return ret;
}

/* All options */

int WtSessionHandlerInternal(WtSession *s,
    Tcl_Obj *handler, Tcl_Obj *method, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, i, len, cmdObjc, cmdOffset, n;
  Tcl_Obj *cmd, **argv, *err, *res;
  const char *handlerStr, *handlerTypeStr = NULL;
  WtContext *w = WtGetAssocContext(interp);

  if (WtPrepareSessionHandler(s, method, interp)) {

    /* Build the command */

    cmd = Tcl_NewStringObj(NULL, 0);
    Tcl_IncrRefCount(cmd);

    if (s->handlerType) {
      handlerTypeStr = WtToString(s->handlerType);
    }

    if (handlerTypeStr && !strcmp(handlerTypeStr, "namespace")) {
      len = Tcl_GetCharLength(s->handler);
      if (len >= 2) {
        handlerStr = WtToString(s->handler);
        if (!(handlerStr[len - 1] == ':' && handlerStr[len - 2] == ':')) {
          Tcl_AppendToObj(cmd, "::", 2);
        }
      }
      Tcl_AppendObjToObj(cmd, method);

      cmdObjc = objc + 1;
      n = sizeof(Tcl_Obj *) * cmdObjc;
      argv = (Tcl_Obj **)ckalloc(n);
      argv[0] = cmd;
    } else {
      Tcl_AppendObjToObj(cmd, s->handler);

      cmdObjc = objc + 2;
      n = sizeof(Tcl_Obj *) * cmdObjc;
      argv = (Tcl_Obj **)ckalloc(n);
      argv[0] = cmd;
      argv[1] = method;
    }

    cmdOffset = cmdObjc - objc;

    n = objc * sizeof(Tcl_Obj *);
    memcpy(argv + (cmdObjc - objc), objv, n);

    for (i = 0; i < cmdObjc; i++) {
      Tcl_IncrRefCount(argv[i]);
    }

    /* Evaluate the command */

    ret = Tcl_EvalObjv(interp, cmdObjc, argv, TCL_EVAL_DIRECT);

    err = Tcl_GetVar2Ex(interp, "errorInfo", NULL,
     TCL_GLOBAL_ONLY);
    res = Tcl_GetObjResult(interp);

    WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, w,
      "Wtcl: WtSessionHandlerInternal: cmdObjc = %d, ret = %d.",
      cmdObjc, ret);

    for (i = 0; i < cmdObjc; i++) {
      Tcl_DecrRefCount(argv[i]);
    }

    ckfree((char *)argv);
    Tcl_DecrRefCount(cmd);
  }

  return ret;
}

/* Prepare the handler before passing along any commands */

int WtPrepareSessionHandler(WtSession *s, Tcl_Obj *method,
    Tcl_Interp *interp)
{
  int ok = 1;
  const char *methodStr = WtToString(method);

  if (!Tcl_GetCharLength(s->handler)) {
    Tcl_AppendResult(interp, "No session handler defined.", NULL);
    ok = 0;
  } else {
    /* Load the handler */

    if (!s->handlerLoaded && strcmp(methodStr, "loadModule") &&
        strcmp(methodStr, "unloadModule")) {
      ok = WtLoadSessionHandler(s, interp);
    }
  }

  return ok;
}

/* Session usage */

void WtSessionCmdUsage(Tcl_Interp *interp, Tcl_Obj *cmd)
{
  char *cmdStr = WtToString(cmd);

  Tcl_AppendResult(interp,
    wtBadUsagePrefix2,
    cmdStr, " id\n",
    cmdStr, " count\n",
    cmdStr, " has key\n",
    cmdStr, " get key ?defaultValue?\n",
    cmdStr, " set key value\n",
    cmdStr, " setDefault key value\n",
    cmdStr, " add key value\n",
    cmdStr, " addList key list\n",
    cmdStr, " keys\n",
    cmdStr, " values\n",
    cmdStr, " remove key\n",
    cmdStr, " removeList keys\n",
    cmdStr, " array arrayName\n",
    cmdStr, " table\n",
    cmdStr, " copy\n",
    cmdStr, " clear\n",
    cmdStr, " isModified\n",
    cmdStr, " commit\n",
    cmdStr, " clearCache\n",
    cmdStr, " status ?id?\n",
    cmdStr, " delete ?id?\n",
    cmdStr, " clientId\n",
    cmdStr, " newId\n",
    cmdStr, " cookie ?cookie?\n",
    cmdStr, " cookieName ?name?\n",
    cmdStr, " cookieSendMode ?mode?\n",
    cmdStr, " maxIdleTime ?time?\n",
    cmdStr, " sweepFrequency ?frequency?\n",
    cmdStr, " handler ?handler?\n",
    cmdStr, " handlerType ?type?\n",
    NULL);
}
